package com.talkweb.osmharmony.tileprovider;

import com.talkweb.osmharmony.api.IMapView;
import com.talkweb.osmharmony.tileprovider.modules.ConfigurablePriorityThreadFactory;
import com.talkweb.osmharmony.util.Log;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.ImageInfo;

import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class PixelMapPool {

    private final LinkedList<PixelMap> mPool = new LinkedList<>();
    private final ExecutorService mExecutor = Executors.newFixedThreadPool(1,
            new ConfigurablePriorityThreadFactory(Thread.MIN_PRIORITY, getClass().getName()));

    //singleton: begin
    private PixelMapPool() {
    }

    private static final PixelMapPool sInstance = new PixelMapPool();

    public static PixelMapPool getInstance() {
        return sInstance;
    }
    //singleton: end

    public void returnDrawableToPool(ReusablePixelMapElement drawable) {
        PixelMap pixelMap = drawable.tryRecycle();
        if (pixelMap != null && !pixelMap.isReleased() && pixelMap.isEditable() && pixelMap.getImageInfo() != null) {
            synchronized (mPool) {
                mPool.addLast(pixelMap);
            }
        } else if (pixelMap != null) {
            Log.d(IMapView.LOGTAG, "Rejected bitmap from being added to BitmapPool.");
        }
    }

    /**
     * @deprecated As of 6.0.2, use
     * {@link #applyReusableOptions(ImageSource.DecodingOptions, int, int)} instead.
     */
    @Deprecated
    public void applyReusableOptions(final ImageSource.DecodingOptions aDecodingOptions) {
        // We can not guarantee a bitmap can be reused without knowing the dimensions, so always
        // return null in inBitmap
        aDecodingOptions.allowPartialImage = true;
        aDecodingOptions.sampleSize = ImageSource.DecodingOptions.DEFAULT_SAMPLE_SIZE;
        aDecodingOptions.editable = true;
    }

    public void applyReusableOptions(final ImageSource.DecodingOptions aDecodingOptions, final int width, final int height) {
            // This could be optimized for KK and up, as from there on the only requirement is that
            // the reused bitmap's allocatedbytes are >= the size of new one. Since the pool is
            // almost only used for tiles of the same dimensions, the gains will probably be small.
//        aDecodingOptions.inBitmap = obtainSizedBitmapFromPool(width, height);
        aDecodingOptions.allowPartialImage = true;
        aDecodingOptions.sampleSize = ImageSource.DecodingOptions.DEFAULT_SAMPLE_SIZE;
        aDecodingOptions.editable = true;
    }

    /**
     * @deprecated As of 6.0.2, use
     * {@link #obtainSizedBitmapFromPool(int, int)} instead.
     */
    @Deprecated
    public PixelMap obtainBitmapFromPool() {
        synchronized (mPool) {
            if (mPool.isEmpty()) {
                return null;
            } else {
                final PixelMap pixelMap = mPool.removeFirst();
                if (pixelMap.isReleased()) {
                    return obtainBitmapFromPool(); // recurse
                } else {
                    return pixelMap;
                }
            }
        }
    }

    public PixelMap obtainSizedBitmapFromPool(final int aWidth, final int aHeight) {
        synchronized (mPool) {
            if (mPool.isEmpty()) {
                return null;
            } else {
                for (final PixelMap pixelMap : mPool) {
                    if (pixelMap.isReleased()) {
                        mPool.remove(pixelMap);
                        return obtainSizedBitmapFromPool(aWidth, aHeight); // recurse to prevent ConcurrentModificationException
                    } else if (isSampleWidthAndHeight(pixelMap, aWidth, aHeight)) {
                        mPool.remove(pixelMap);
                        return pixelMap;
                    }
                }
            }
        }

        return null;
    }

    private boolean isSampleWidthAndHeight(PixelMap pixelMap, final int aWidth, final int aHeight) {
        ImageInfo imageInfo = pixelMap.getImageInfo();
        if (imageInfo != null && imageInfo.size != null) {
            return imageInfo.size.width == aWidth && imageInfo.size.height == aHeight;
        }
        return false;
    }

    public void clearBitmapPool() {
        synchronized (sInstance.mPool) {
            while (!sInstance.mPool.isEmpty()) {
                PixelMap pixelMap = sInstance.mPool.remove();
                pixelMap.release();
            }
        }
    }

    /**
     * @since 6.0.0
     * The same code was duplicated in many places: now there's a unique entry point and it's async
     */
    public void asyncRecycle(final Element pElement) {
        if (pElement == null) {
            return;
        }
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                syncRecycle(pElement);
            }
        });
    }

    /**
     * @since 6.0.0
     */
    private void syncRecycle(final Element pElement) {
        if (pElement == null) {
            return;
        }

//        if (pElement instanceof PixelMapElement) {
//            final PixelMap pixelMap = ((PixelMapElement) pElement).getPixelMap();
//            if (pixelMap != null) {
//                pixelMap.release();
//            }
//        }

        if (pElement instanceof ReusablePixelMapElement) {
            returnDrawableToPool((ReusablePixelMapElement) pElement);
        }
    }
}
